// depends on Selector and DOM

(function(){

// jQuery-like node list object
	function NodeList(selector, fromElement) {
		if (selector) {
			NW.Dom.select(selector, fromElement, this, Element.extend);
		}
	}
	var slice = Array.prototype.slice;
	NodeList.prototype = {
		each: function each(iterator) {
			for (var i = 0, len = this.length; i < len; i++) {
				iterator.call(this[i], i, this[i]);
			}
			return this;
		},
		size: function size() {
			return this.length;
		},
		indexOf: function indexOf(el) {
			return Array.prototype.indexOf.call(this, el);
		},
		item: function item(i) {
			return this[i];
		},
		first: function first() {
			return this[0];
		},
		last: function last() {
			return this[this.length - 1];
		},
		toArray: function toArray() {
			return slice.call(this, 0);
		},
		slice: function slice(from, len) {
			return slice.call(this, from, len);
		},
		push: function push() {
			var args = slice.call(arguments);
			return Array.prototype.push.call(this, args);
		},
		sort: function sort() {
			var args = slice.call(arguments);
			return Array.prototype.push.call(this, args);
		},
		splice: function splice(from, idx, add) {
			return Array.prototype.splice.call(this, from, idx, add);
		},
		concat: function concat() {
			var args = slice.call(arguments);
			return Array.prototype.concat.apply(this, args);
		}
	};
	if (!Array.prototype.indexOf) {
		Array.prototype.indexOf = function indexOf(item, i) {
			i || (i = 0);
			var length = this.length;
			if (i < 0) { i = length + i; }
			for (; i < length; i++) {
				if (this[i] === item) { return i; }
			}
			return -1;
		};
	}
	function $j(selector, fromElement) {
		return new NodeList(selector, fromElement);
	}
	ProtoLove.NodeList = NodeList;
	ProtoLove.$j = $j;
	window.NodeList = window.NodeList || NodeList;
	window.$j = window.$j || $j;

	function iterateList(list, iterator) {
		var arr = list.split(' '), i = 0, item;
		while ((item = arr[i++])) {
			iterator.call(null, item, i);
		}
	}

	Element.addMethods({
		bind: Element.Methods.observe,
		unbind: Element.Methods.stopObserving
	});

  // Element methods/native methods that can be called for every element in the node list
  var applyAll = 'setStyle setOpacity addClassName removeClassName toggleClassName hide show remove replaceChild update ' +
    'removeAttribute setAttribute prependChild appendChild insertBefore observe stopObserving bind unbind delegate';

  iterateList(applyAll, function(method) {
    NodeList.prototype[method] = /** @ignore */ function() {
    	var i, args = slice.call(arguments), len = this.length;
      for (i = 0; i < len; i++) {
				this[i][method].apply(this[i], args);
      }
      return this;
    };
  });

//  Methods that return an array should return a concatenation of NodeList objects
//  var nodeListReturners = 'getNextSiblings getPrevSiblings getSiblings ' +
//    'getChildren getElementsByTagName getElementsByClassName getAncestors select';
//  $p.each(nodeListReturners, function(method) {
//    NodeList.prototype[method] = (function(fnName) {
//      return function() {
//        var results = [], ret, i, cls;
//        for (i = 0; i < this.length; i++) {
//        	cls = pulp.Node._getTagClassName(this[i].nodeName);
//        	if (typeof pulp.Node[cls].prototype[method] == 'function') {
//        		ret = pulp.Node[cls].prototype[fnName].apply(this.raw[i], slice.call(arguments));
//        		results = result.concat(ret);
//        	}
//        	else {
//        		results.push(undefined);
//        	}
//        }
//        new NodeList($p.arrayUnique(results));
//      };
//    })(method);
//  });

	// Methods that return an array should return a concatenation of NodeList objects
  var nodeListReturners = 'getElementsByTagName getElementsByClassName select';
  iterateList(nodeListReturners, function(method) {
    NodeList.prototype[method] = /** @ignore **/ function() {
			var i, nodes = new NodeList, args = slice.call(arguments), len = this.length;
			for (i = 0; i < len; i++) {
				nodes[i] = this[i][method].apply(this[i], args);
			}
			nodes.length = i + 1;
			return nodes;
    };
  });

  // Methods that return a single node should return NodeList object
  var nodeListReturners = 'up down cloneNode';
  iterateList(nodeListReturners, function(method) {
    NodeList.prototype[method] = /** @ignore **/ function() {
			var i, nodes = new NodeList, args = slice.call(arguments), len = this.length;
			for (i = 0; i < len; i++) {
				nodes[i] = this[i][method].apply(this[i], args);
			}
			nodes.length = i + 1;
			return nodes;
    };
  });

  // Methods that make sense to be applied only to the first member of the collection
  var applyFirst = 'identify getAttribute hasAttribute hasClassName match';
  iterateList(applyFirst, function(method) {
    NodeList.prototype[method] = /** @ignore **/ function() {
			if (this[0]) {
				var args = slice.call(arguments);
				return this[0][method].apply(this[0], args);
			}
			return undefined;
		};
  });

	// support for jQuery-like event attachment
	var events = 'DOMContentLoaded blur change click dblclick error focus focusin focusout' +
		' keydown keypress keyup mousedown mouseenter mouseleave mousemove mouseout mouseover mouseup' +
		' resize scroll select submit';
	iterateList(events, function(event) {
		NodeList.prototype[event] = /** @ignore **/ function(handler) {
			var len = this.length;
			for (var i = 0; i < len; i++) {
				this[i].observe(event, handler);
			}
			return this;
		};
	});

})();